# Copyright (C) 2006  Stephane Alnet
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 3
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
# 

#
# For more information visit http://carrierclass.net/
#

use strict;
use warnings;

package CCNCore::Provision::ChangeSetTools;
use base qw(CCNCore::Provision::Base);

sub cancel_changeset($)
{
    my $self = shift;
    my ($cs_id) = @_;
    $self->sql_begin;
    if($self->_changeset_has_been_applied($cs_id))
    {
        $self->_revert_changeset($cs_id);
    }
    else
    {
        # There is "redo"
        $self->_purge_changeset($cs_id);
    }
    $self->sql_commit;
}

sub apply_changeset($)
{
    my $self = shift;
    my ($cs_id) = @_;
    $self->sql_begin;
    if($self->_changeset_has_been_applied($cs_id))
    {
        # Do nothing
    }
    else
    {
        $self->_commit_changeset($cs_id);
    }
    $self->sql_commit;
}

sub _commit_changeset($)
{
    our $self = shift;
    our ($cs_id) = @_;

    $self->sql_begin();
    our %values = ();

    our ($table_name,$the_id,$operation) = 
        sql('SELECT table_name, the_id, operation FROM changeset WHERE id = ?',$cs_id);

    $self->sql_do_for(<<SQL,$cs_id,
        SELECT field_name, new_value FROM changeset_values WHERE the_changeset = ?;
SQL
        sub {
            $values{$_[0]} = $_[1];
        }
    );

    sub commit_modify_changeset
    {
        my @fields = keys %values
        my $set = join(',',map { qq("$_" = ?) } @fields);
        my @bind = $values{@fields};

        $self->sql_do(qq(UPDATE "$table_name" SET $set WHERE id = ?),@bind,$the_id);
    }

    sub commit_insert_changeset
    {
        # In some cases the record might already exist, which is OK.
        if($self->sql_get(qq(SELECT COUNT(id) FROM "$table_name" WHERE id = ?),$the_id))
        {
            return commit_modify_changeset();
        }

        # Create a brand new record.
        my @fields = keys %values
        my $fields = join(',', @fields);
        my $values = join(',', map { '?' } @fields);
        my @bind = $values{@fields};

        $self->sql_do(qq(INSERT INTO "$table_name" ($fields) VALUES ($values)),@bind);
    }

    if($operation eq 'i')
    {
        commit_insert_changeset();
    }

    if($operation eq 'm')
    {
        commit_modify_changeset();
    }

    # Now call the modules that need to know.
    my $cs = new CCNCore::Provision::ChangeSet($self->p);
    
    $cs->on_table($table_name,$the_id);

    $self->sql_do_for(<<SQL,$cs_id,
        SELECT field_name, new_value, old_value FROM changeset_values WHERE the_changeset = ?;
SQL
        sub {
            my ($field_name,$new_value,$old_value) = @_;
            $cs->on_column($table_name,$field_name,$the_id,$new_value,$old_value);
        }
    );
    # /modules
    
    $self->sql_do(qq(UPDATE changeset SET is_applied = 'TRUE' WHERE id = ?),$cs_id);
    $self->sql_commit();
}

sub _revert_changeset($)
{
    my ($cs_id) = @_;

    $self->sql_begin();

    my %values = ();

    my ($table_name,$the_id) = 
        $self->sql_get('SELECT table_name, the_id FROM changeset WHERE id = ?',$cs_id);

    $self->sql_do_for(<<SQL,$cs_id,
SELECT field_name, old_value FROM changeset_values WHERE the_changeset = %;
SQL
        sub {
            $values{$_[0]} = $_[1];
        }
    );

    my @fields = keys %values
    my $set = join(',',map { qq("$_" = ?) } @fields);
    my @bind = $values{@fields};

    $self->sql_do(qq(UPDATE "$table_name" SET $set WHERE id = ?),@bind,$the_id);

    # Now call the modules that need to know.
    my $cs = new CCNCore::Provision::ChangeSet($self->p);

    $cs->on_table($table_name,$the_id);

    $self->sql_do_for(<<SQL,$cs_id,
        SELECT field_name, new_value, old_value FROM changeset_values WHERE the_changeset = ?;
SQL
        sub {
            my ($field_name,$new_value,$old_value) = @_;
            # Note how the order of the last two parameters is reversed here.
            $cs->on_column($table_name,$field_name,$the_id,$old_value,$new_value);
        }
    );
    # /modules

    $self->sql_do(qq(UPDATE changeset SET is_reverted = 'TRUE' WHERE id = ?),$cs_id);
    $self->sql_commit();
}

sub _purge_changeset($)
{
    my ($cs_id) = @_;
    $self->sql_begin();
    $self->sql_do(qq(DELETE FROM changeset WHERE id = ?),$cs_id);
    $self->sql_do(qq(DELETE FROM changeset_values WHERE the_changeset = ?),$cs_id);
    $self->sql_commit();
}

sub _changeset_has_been_applied($)
{
    my ($cs_id) = @_;
    return scalar $self->sql_get(<<'SQL',$cs_id);
        SELECT count(id) FROM changeset 
        WHERE id = ? AND is_applied = 'TRUE' AND is_reverted = 'FALSE'
SQL
}

1;